package analysesequence;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.StopInstancesRequest;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.*;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

/**
 *
 * @author lorddoskias
 */
public class Analysesequence {

    public static List<String> uploadedFiles = new ArrayList<String>();

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {

        String dstFile = args[0];
        String prefix = args[1];
        String email1 = args[2];
        String email2 = args[3];
        String hashValue = args[4];
        String readType = args[5];

        String privatePath = null;


        try {
            privatePath = createUserDir(prefix);
        } catch (IOException e) {
            System.out.println("Error creating user directory");
            e.printStackTrace();
            stopInstance(getInstanceIdFromTag("Name", "big instance"));
        }


        //HASH PROGRAM STARTS HERE
        try {
            hashData(dstFile, hashValue, readType, privatePath);
        } catch (IOException e) {
            System.out.println("Error while hashing");

            e.printStackTrace();


        } catch (InterruptedException e) {
            System.out.println("Hashing interrupted");

            e.printStackTrace();

        }

        System.out.println("VevetH success");

        //ANALYSIS program starts here
        try {
            analyseData(privatePath);
        } catch (IOException e) {
            System.out.println("Error while analysing");
            e.printStackTrace();


        } catch (InterruptedException e) {
            System.out.println("Analysis interrupted");

            e.printStackTrace();

        }

        System.out.println("VevetG success");
        try {
            uploadResult(privatePath, prefix);
        } catch (IOException ex) {
            System.out.println("Error uploading files to AWS");

        }


        try {
            sendMail(email1, email2);
        } catch (NoSuchProviderException ex) {
            System.out.println("Cannot find smtp host");
            Logger.getLogger(Analysesequence.class.getName()).log(Level.SEVERE, null, ex);



        } catch (AddressException ex) {
            System.out.println("error authenticating with smt server");
            Logger.getLogger(Analysesequence.class.getName()).log(Level.SEVERE, null, ex);


        } catch (MessagingException ex) {
            System.out.println("general error");
            Logger.getLogger(Analysesequence.class.getName()).log(Level.SEVERE, null, ex);



        }

        cleanInterim(privatePath);

        try {
            stopInstance(getInstanceIdFromTag("Name", "big instance"));
        } catch (IOException ex) {
            //send some email with more info about the error;
            Logger.getLogger(Analysesequence.class.getName()).log(Level.SEVERE, null, ex);
            return;
        }


    }

    // ./velveth /root/velvet_res/ 25 -fasta.gz -short /home/dsk/Downloads/sra_data.fasta.gz
    /**
     * executes velveth
     * @param dstFile - path to fasta file
     * @param result - directory where to store results
     * @throws IOException
     * @throws InterruptedException 
     */
    private static void hashData(String dstFile, String hashValue, String readType, String result) throws IOException, InterruptedException {
    
        List<String> command = new ArrayList<String>();

        command.add(Constants.VELVETH_PATH);

        command.add(result);

        command.add(hashValue);

        command.add("-fasta.gz");

        command.add(readType);

        command.add(dstFile);

        ProcessBuilder pb = new ProcessBuilder(command);

        System.out.println("velveth is " + pb.toString());

        Process p = pb.start();

        BufferedReader bf = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;

        p.waitFor();

        while ((line = bf.readLine()) != null) {
            System.out.println(line);
        }

        System.out.println("Hashing completed successfully ");
    }

    private static String createUserDir(String dir) throws IOException {
        File f = new File(Constants.RESULT_DIR, dir);
        if (!f.isDirectory()) {
            if (!f.mkdir()) {
                throw new IOException();
            }
        }

        return f.getAbsolutePath();
    }

    /**
     * Executes velvetG 
     * @param result - directory of velvetH output
     * @throws IOException
     * @throws InterruptedException 
     */
    private static void analyseData(String result) throws IOException, InterruptedException {

         
        // ./velvetg /root/velvet_res/ -exp_cov auto -cov_cutoff auto -min_contig_lgth 100 -unused_reads yes
        List<String> command = new ArrayList<String>();

        command.add(Constants.VELVETG_PATH);

        command.add(result);

        command.add("-exp_cov");
        command.add("auto");

        command.add("-cov_cutoff");
        command.add("auto");

        command.add("-read_trkg");
        command.add("yes");

        command.add("-amos_file");
        command.add("yes");

        command.add("-unused_reads");
        command.add("yes");

        //ProcessBuilder pb = new ProcessBuilder(command, arg1, arg2, subarg1, arg3, subarg2, arg4, subarg3, arg5, subarg4);
        ProcessBuilder pb = new ProcessBuilder(command);
        System.out.println("velveth is " + pb.toString());
        Process p = pb.start();
        BufferedReader bf = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String line;

        p.waitFor();

        while ((line = bf.readLine()) != null) {
            System.out.println(line);
        }

        System.out.println("Analysis completed successfully");
    }

    /**
     * Upload everything from a directory to S3
     * @param path - path to result directory 
     * @param prefix - prefix under which to store files in S3
     * @throws IOException 
     */
    private static void uploadResult(String path, String prefix) throws IOException {

        File resultDir = new File(path);

        //make it in the form of user_name/some-number
        String uniqueId = prefix + "/" + System.currentTimeMillis();

        //FILE FILTER
        FileFilter fileFilter = new FileFilter() {

            @Override
            public boolean accept(File file) {
                return file.isFile() && !file.getName().startsWith(".");
            }
        };
        //FILE FILTER

        File[] files = resultDir.listFiles(fileFilter);

        if (files == null || files.length == 0) {
            throw new IllegalArgumentException("Empty result set");
        } else {
            for (int i = 0; i < files.length; i++) {
                // Get filename of file or directory
                uploadToAws(files[i], uniqueId);
            }
        }
    }

    /**
     * Upload a file to AWS repository
     * @param f - reference to the file to be uploaded
     * @param prefix - prefix of the user (used for security separation)
     * @throws IOException 
     */
    public static void uploadToAws(File f, String prefix) throws IOException {

        AmazonS3 s3 = Constants.getS3Obj();

        try {

            String relativeS3Path = prefix + "/" + f.getName();
            PutObjectRequest por = new PutObjectRequest(Constants.BUCKET, relativeS3Path, f);
            por.setCannedAcl(CannedAccessControlList.PublicRead);
            s3.putObject(por);

            uploadedFiles.add(relativeS3Path);

        } catch (AmazonServiceException ase) {
            System.out.println("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon S3, but was rejected with an error response for some reason.");
            System.out.println("Error Message:    " + ase.getMessage());
            System.out.println("HTTP Status Code: " + ase.getStatusCode());
            System.out.println("AWS Error Code:   " + ase.getErrorCode());
            System.out.println("Error Type:       " + ase.getErrorType());
            System.out.println("Request ID:       " + ase.getRequestId());
        } catch (AmazonClientException ace) {
            System.out.println("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with S3, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message: " + ace.getMessage());
        }
    }

    /**
     * Stops an EC2 instance based on id
     * @param id - unique id
     * @throws IOException 
     */
    public static void stopInstance(String id) throws IOException {

        AmazonEC2Client ec2 = Constants.getEC2Obj();

        //we work in the EU region
        ec2.setEndpoint("ec2.eu-west-1.amazonaws.com");
        //list containing which instances to stop
        List<String> instances = new ArrayList<String>();
        //take list of isntances
        DescribeInstancesResult res = ec2.describeInstances();
        //take the first instance
        instances.add(id);
        //create a request object
        StopInstancesRequest req = new StopInstancesRequest();
        //asign list to request
        req.setInstanceIds(instances);
        //exec request
        ec2.stopInstances(req);
    }

    /**
     * Send e-mail to 2 addresses
     * @param mail1 - email 1
     * @param mail2 - email 1
     * @throws NoSuchProviderException
     * @throws AddressException
     * @throws MessagingException 
     */
    public static void sendMail(String mail1, String mail2) throws NoSuchProviderException, AddressException, MessagingException {
        Properties props = new Properties();

        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtps.host", Constants.SMTP_HOST_NAME);
        props.put("mail.smtps.auth", "true");
        // props.put("mail.smtps.quitwait", "false");

        Session mailSession = Session.getDefaultInstance(props);
        mailSession.setDebug(true);
        Transport transport = mailSession.getTransport();

        MimeMessage message = new MimeMessage(mailSession);
        message.setSubject("Analysis: Complete");
        message.setContent(buildMessageBody(), "text/plain");
        message.setFrom(new InternetAddress(Constants.SMTP_AUTH_USER));
        message.addRecipient(Message.RecipientType.TO,
                new InternetAddress(mail1));
        message.addRecipient(Message.RecipientType.CC,
                new InternetAddress(mail2));

        transport.connect(Constants.SMTP_HOST_NAME, Constants.SMTP_HOST_PORT, Constants.SMTP_AUTH_USER, Constants.SMTP_AUTH_PWD);

        transport.sendMessage(message,
                message.getRecipients(Message.RecipientType.TO));

        transport.sendMessage(message,
                message.getRecipients(Message.RecipientType.CC));
        transport.close();
    }

    private static String buildMessageBody() {
        String body = "This is an automatic email, don't respond!\n\n\n";
        body += "The following is a list of files that have been processed: \n";
        for (String filePath : uploadedFiles) {
            body += "http://nib9-storage.s3.amazonaws.com/" + filePath;
            body += "\n\n";
        }

        body += "\n\nend of message\n";

        return body;
    }

    public static void cleanInterim(String path) {

        File[] files = new File(path).listFiles();

        if (files == null || files.length == 0) {
            return;
        }

        for (File f : files) {
            f.delete();
        }
    }

    public static String getInstanceIdFromTag(String tagName, String tagValue) throws IOException {

        AmazonEC2Client ec2 = Constants.getEC2Obj();
        ec2.setEndpoint("ec2.eu-west-1.amazonaws.com");

        DescribeInstancesResult res = ec2.describeInstances();

        List<Reservation> reservations = res.getReservations();

        for (Reservation reservation : reservations) {
            List<Instance> instances = reservation.getInstances();
            for (Instance instance : instances) {
                List<Tag> tags = instance.getTags();
                for (Tag tag : tags) {
                    if (tag.getKey().equals(tagName) && tag.getValue().equals(tagValue)) {
                        return instance.getInstanceId();

                    }
                }
            }
        }

        return null;
    }
}
